﻿package com.d4p.util
{
	import flash.filters.ColorMatrixFilter;
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	
	public class AdjustColor
	{
		private static const MIN_BRIGHTNESS:Number = -100;
		private static const MAX_BRIGHTNESS:Number = 100;
		
		private static const MIN_CONTRAST:Number = -100;
		private static const MAX_CONTRAST:Number = 100;
		
		private static const MIN_SATURATION:Number = -100;
		private static const MAX_SATURATION:Number = 100;
		
		private static const MIN_HUE:Number = -180;
		private static const MAX_HUE:Number = 180;
		
		//saturation const.
		private static const R_LUM:Number = - 0.009258;
		private static const G_LUM:Number = - 0.018282;
		private static const B_LUM:Number = - 0.00246;				
		
		//contrast const.
		private static  var DELTA_INDEX:Array = [ 	 0,    0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1,  0.11,
															 0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24,
															 0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42,
															 0.44, 0.46, 0.48, 0.5,  0.53, 0.56, 0.59, 0.62, 0.65, 0.68, 
															 0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98,
															 1.0,  1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54,
															 1.60, 1.66, 1.72, 1.78, 1.84, 1.90, 1.96, 2.0,  2.12, 2.25, 
															 2.37, 2.50, 2.62, 2.75, 2.87, 3.0,  3.2,  3.4,  3.6,  3.8,
															 4.0,  4.3,  4.7,  4.9,  5.0,  5.5,  6.0,  6.5,  6.8,  7.0,
															 7.3,  7.5,  7.8,  8.0,  8.4,  8.7,  9.0,  9.4,  9.6,  9.8, 
															 10.0 ];
		//matrix				
		private static const IDENTITY:Array = [ 1, 0, 0, 0, 0,
														0, 1, 0, 0, 0,
														0, 0, 1, 0, 0,
														0, 0, 0, 1, 0 ];
		private var target:DisplayObject;
		private var matrix:Array;
		private var adjustmentList:Array;	// adjustment list.
		
		private var brightness:Number;
		private var contrast:Number;
		private var saturation:Number;
		private var hue:Number;
		
		private var brightnessMatrix:Array;
		private var contrastMatrix:Array;
		private var saturationMatrix:Array;
		private var hueMatrix:Array;
		
		/**
		 * 생성자
		 * @param obj
		 * @return 
		 * 
		 */		
		public function AdjustColor(obj:DisplayObject = null, 
									brightness:Number=0, 
									contrast:Number=0,
									saturation:Number=0,
									hue:Number=0)
		{
			this.matrix = IDENTITY.concat();
			this.adjustmentList = new Array();
			this.brightnessMatrix = new Array();
			this.contrastMatrix = new Array();
			this.saturationMatrix = new Array();
			this.hueMatrix = new Array();
			setTarget(obj);
			setBrightness(brightness);
			setContrast(contrast);
			setSaturation(saturation);
			setHue(hue);
		}
		
		public function setTarget(dis:DisplayObject):void
		{
			this.target = dis;
		}
		
		public function getTarget():DisplayObject
		{
			return this.target;
		}
		

		
		/**
		 * 밝기 조정 매서드
		 * -100 ~ 100
		 * @param value
		 * 
		 */		
		public function setBrightness(value:Number=0):void
		{
			this.brightness = checkValue(MIN_BRIGHTNESS, MAX_BRIGHTNESS, value);
			//if(value == 0) return;
			
			var _matrix:Array = new Array();
			_matrix = _matrix.concat( [1, 0, 0, 0, brightness] );	// red
			_matrix = _matrix.concat( [0, 1, 0, 0, brightness] );	// green
			_matrix = _matrix.concat( [0, 0, 1, 0, brightness] );	// blue
			_matrix = _matrix.concat( [0, 0, 0, 1, 0] );	// alpha

			if(brightnessMatrix.length != 0){
				var check:Boolean = checkEqualityOfMatrix(brightnessMatrix, IDENTITY);
				if(check == false) divideMatrix(brightnessMatrix);
			}			
			multipleMatrix(_matrix);		// this.matrix에 곱함
			brightnessMatrix = transformMatrixData(_matrix);
			
			if(getTarget() != null) applyFilter(getTarget());
		}
		public function getBrightness():Number
		{
			return this.brightness;
		}


		/**
		 * 명도 조정 매서드 
		 * -100 ~ 100 값을 0 ~ 3 범위값으로 변환
		 * @param value
		 * 
		 */		
		public function setContrast(value:Number=0):void
		{
			this.contrast = checkValue(MIN_CONTRAST, MAX_CONTRAST, value);
			//if(value == 0) return;	
			
			var x:Number;
			if (contrast < 0) {
				x = 127 + contrast / 100 * 127;
			} else {
				x = contrast % 1;
				if (x == 0) {
					x = DELTA_INDEX[contrast];
				} else {
					x = DELTA_INDEX[(contrast<<0)] * (1 - x) + DELTA_INDEX[(contrast<<0) + 1] * x;
				}
				x = x*127+127;
			}
			
			var scale:Number = x / 127;
			var offset:Number = 0.5 * (127 - x);
			
			var _matrix:Array = new Array();
			_matrix = _matrix.concat( [scale, 	0, 		0, 			0, 		offset] );	// red
			_matrix = _matrix.concat( [0, 		scale, 	0,			0, 		offset] );	// green
			_matrix = _matrix.concat( [0, 		0, 		scale, 		0, 		offset] );	// blue
			_matrix = _matrix.concat( [0, 		0, 		0,			1, 		0] );	// alpha
			
			
			if(contrastMatrix.length != 0){
				var check:Boolean = checkEqualityOfMatrix(contrastMatrix, IDENTITY);
				if(check == false) divideMatrix(contrastMatrix);
			}
			multipleMatrix(_matrix);	
			contrastMatrix = transformMatrixData(_matrix);
			
			if(getTarget() != null) applyFilter(getTarget());
		}		
		
		public function getContrast():Number
		{
			return this.contrast;
		}
		
		
		
		/**
		 * 채도 조정 매서드
		 * -100 ~ 100
		 * @param value
		 * 
		 */		
		public function setSaturation(value:Number=0):void
		{
			this.saturation = checkValue(MIN_SATURATION, MAX_SATURATION, value);
			
			var r1:Number = R_LUM * saturation;
			var r2:Number =(R_LUM + 0.03) * saturation;
			var g1:Number = G_LUM * saturation;
			var g2:Number =(G_LUM + 0.03) * saturation;
			var b1:Number = B_LUM * saturation;
			var b2:Number =(B_LUM + 0.03) * saturation;
			
			if(saturation < 0){
				r1 = r1 / 3;
				r2 = r2 / 3;
				g1 = g1 / 3;
				g2 = g2 / 3;
				b1 = b1 / 3;
				b2 = b2 / 3;
			}
	
			var _matrix:Array = new Array();
			_matrix = _matrix.concat( [1+r2, 		g1, 		b1, 		0, 			0] );	// red
			_matrix = _matrix.concat( [r1, 			1+g2, 		b1, 		0, 			0] );	// green
			_matrix = _matrix.concat( [r1, 			g1, 		1+b2, 		0, 			0] );	// blue
			_matrix = _matrix.concat( [0,  			0,   		0,  		1, 			0] );	// alpha
			
			if(saturationMatrix.length != 0){
				var check:Boolean = checkEqualityOfMatrix(saturationMatrix, IDENTITY);
				if(check == false) divideMatrix(saturationMatrix);
			}
			multipleMatrix(_matrix);
			this.saturationMatrix = transformMatrixData(_matrix);
			
			if(getTarget() != null) applyFilter(getTarget());
		}


		public function getSaturation():Number
		{
			return this.saturation;
		}
		
	
		/**
		 * Hue값 조정 매서드
		 * @param value
		 * 
		 */		
		public function setHue(value:Number):void
		{
			/*
			if(getTarget() != null) applyFilter(getTarget());
			if(value == 0) return;
			*/
		}

		
		/**
		 * 기존 matrix에 새 matrix를 적용함
		 * 
		 * @param		Array		변경된 matrix
		 * 
		 */			
		private function multipleMatrix(mat:Array):void
		{		
			var temp:Array = new Array ();
			var i:uint = 0;
			var aa:Number;	// A*B == B*A를 만족 시키기 위한 값  
			
			if(matrix[0] != 1){
				if(contrastMatrix[0] != null) aa = contrastMatrix[0];
			}else{
				aa = matrix[0];
			}
			
			for (var y:uint = 0; y < 4; y++ )
			{
				for (var x:uint = 0; x < 5; x++ )
				{
					temp[i + x] = mat[ i ] * matrix[ x ] + 
							   	     mat[ i + 1 ] * matrix[ x + 5 ] + 
							         mat[ i + 2 ] * matrix[ x + 10 ] + 
							         mat[ i + 3 ] * matrix[ x + 15 ] +
							   	     (x == 4 ? mat[ i + 4 ] * aa : 0);
				}
				i += 5;
			}
			//trace("multiple temp: " + transformMatrixData(temp));
			this.matrix = transformMatrixData(temp);			
		}		
		
		/**
		 * 이전에 적용한 matrix를 제거
		 * @param mat
		 * 
		 */		
		private function divideMatrix(mat:Array):void
		{
			var temp:Array = new Array();
			var i:uint = 0;
			var checkMatrix:Boolean = checkEqualityOfMatrix(mat, matrix);
			var aa:Number;	// A*B == B*A를 만족 시키기 위한 값  
			
			if(matrix[0] != 1){
				if(contrastMatrix[0] != null) aa = contrastMatrix[0];
				if(aa == 0) aa = 1;
			}else{
				aa = matrix[0];
			}
			
			//trace(mat, "<==", aa);
			//trace(this.matrix, " <=======");
			for(var y:uint = 0; y < 4; y++)
			{
				for(var x:uint=0; x < 5; x++)
				{
					var r1:Number = (mat[ i ] == 0) ? 0 : (1 / mat[ i ]);
					var g1:Number = (mat[ i + 1 ] == 0) ? 0 : (1 / mat[ i + 1 ]);
					var b1:Number = (mat[ i + 2 ] == 0) ? 0 : (1 / mat[ i + 2 ]);
					var a1:Number = (mat[ i + 3 ] == 0) ? 0 : (1 / mat[ i + 3 ]);
					var o1:Number = - mat[ i + 4 ] * (1 / mat[0]) * aa * (1 / mat[0]); 
					
					if(y==0){
						if(mat[i] == 0) r1 = 1;						
						if(matrix[0] == 0) matrix[0] = 1;
					} 
					else if(y==1){
						if(mat[i+1] == 0) g1 = 1;
						if(matrix[6] == 0) matrix[6] = 1;
					}
					else if(y==2){
						if(mat[i+2] == 0) b1 = 1;	
						if(mat[12] == 0) matrix[12] = 1;
					}
					else if(y==3){
						if(mat[i+3] == 0) a1 = 1;
						if(mat[18] == 0) matrix[18] = 1;
					}
					temp[ i + x ] = (r1 * matrix[ x ]) +
									   (g1 * matrix[ x + 5 ]) +
									   (b1 * matrix[ x + 10 ]) +
									   (a1 * matrix[ x + 15 ]) +
									   (x == 4 ? o1 : 0);
				}
				//trace("=========================");
				i += 5;
			}
			//trace(transformMatrixData(temp), "<-----2");
			
			this.matrix = transformMatrixData(temp);
		}
		
		
		
		/*---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
		
		
		/**
		 * 필터를 target에 적용시키는 매서드
		 * @param matrix
		 * 
		 */	
		
		 
		public function getMatrix():Array
		{
			return this.matrix;
		}
		
		public function applyFilter(dis:DisplayObject):void
		{
			var filter:ColorMatrixFilter = new ColorMatrixFilter(this.matrix);
			var filters:Array = new Array();
			filters.push(filter);
						
			dis.filters = filters;
		}
		
		public function removeFilter(dis:DisplayObject):void
		{
			dis.filters = null;
		}

		
		/*---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
		
		/**
		 * 두 매트릭스가 같은지를 비교
		 * @param mat1
		 * @param mat2
		 * @return 
		 * 
		 */		
		private function checkEqualityOfMatrix(mat1:Array, mat2:Array):Boolean
		{
			var a:String = mat1.join() as String;
			var b:String = mat2.join() as String;
			return a == b
		}

		
		/**
		 * 매트릭스의 data값을 소수점 4자리 까지 표현
		 * @param mat
		 * @return 
		 * 
		 */		
		private function transformMatrixData(mat:Array):Array{
			var tempArray:Array = new Array();
			
			for each(var item:* in mat){
				tempArray.push(Math.floor(Math.round(item * 10000)) / 10000);
			}
			return tempArray;
		}		
		
		
		/**
		 * 입력된 값을 최소, 최대 범위 안의 값으로 리턴하는 매서드
		 * @param minValue		최소값 
		 * @param maxValue		최대값 
		 * @param value			입력값 
		 * @return 
		 * 
		 */		
		private function checkValue(minValue:Number, maxValue:Number, value:Number):Number
		{
			var result:Number = Math.min(maxValue, Math.max(minValue, value));
			return result;
		}
	}
}